//
// Interdomain topology.
//
// @author Michael Liljenstam, ISTS, Dartmouth College
//
// $Id: ASGraph.java,v 1.1 2003/02/27 22:30:40 mili Exp $

package SSF.App.Worm;

import java.io.*;
import java.util.*;

/**
 *  The Internet at the AS (interdomain) level. (Abstracted representation.)
 */
public class ASGraph {

    public boolean DEBUG = false;

    /** Number of ASs - corresponds to: number of strata in 
	epidemiological model. */
    public int numASs = 1;

    /** ASs */
    public AS[] ases;

    /** Lookup table (ASN->index). Translate from ASN to index. */
    public HashMap asLookupFromNum;

    /** Handles different kinds of output of the graph. */
    private OutputManager om = null;

    /** 
     * @param topologyFile Adjacency list format file describing interdomain 
     *                     topology.
     */
    public ASGraph(String topologyFile) {

	// init topology
	try {
	    load(topologyFile);
	} catch (IOException e) {
	    System.err.println("Couldn't load topology file: " + topologyFile);
	    System.err.println(e);
	    System.exit(1);  
	}
    }

    public void createOutputManager(boolean stdOut, boolean globalsOut,
				    boolean doLayout, 
				    String gmlFile, String namFile, 
				    String dmlFile) {
	om = new OutputManager(this, 
			       stdOut, globalsOut, doLayout, 
			       gmlFile, namFile, dmlFile);
    }

    public void closeOutputManager() {
	om.close();
    }

    /** 
     *  Translate ASN to index in ases[].
     */
    public int asnToIndex(int asn) {
	// stratified model - lookup index from lookup table
	return ((Integer) asLookupFromNum.get(new Integer(asn))).intValue();
    }


    /** Output current state using the OutputManager. */
    void output() {
	om.output();
    }

    // state enumeration - for topology file parser
    private static final byte AS_NUM = 0;
    private static final byte AS_OUTDEGREE = 1;
    private static final byte AS_NEIGHBOR = 2;

    /**
     *  Load topology in ASCII file adjacency list format, as downloaded from 
     *  ... (BJ?).
     */
    void load(String fileName) throws IOException {
	// if no filename is given (null), just create one big AS
	if (fileName == null) {
	    if (DEBUG) {
		System.out.println("Homogeneous model. (Not stratfied by AS) => single AS");
	    }

	    int asNumber = 1; /// set to zero
	    AS newAS = new AS(this);
	    newAS.asNumber = asNumber;
	    //	  asLookupFromNum.put(new Integer(asNumber),new Integer(index)); // put into lookup table
	    numASs = 1;

	    // insert into ASGraph array and initialize
	    ases = new AS[numASs];
	    ases[0] = newAS;
	    ases[0].init(0);

	    return;
	}

	// parse input
	asLookupFromNum = new HashMap();

	System.err.println("Loading topology from file: " + fileName);

	StreamTokenizer in = new StreamTokenizer(new InputStreamReader(new FileInputStream(new File(fileName))));
	in.eolIsSignificant(true);

	int type;
	// read past first line
	while ((type = in.nextToken()) != StreamTokenizer.TT_EOF && 
	       (type = in.nextToken()) != StreamTokenizer.TT_EOL) {}

	// read each adjacency list
	byte state = AS_NUM;
	int index = 0;
	LinkedList cellList = new LinkedList();
	while ((type = in.nextToken()) != StreamTokenizer.TT_EOF) {
	    switch(type) {
	    case StreamTokenizer.TT_NUMBER:
		//		System.out.print("#" + (int) in.nval + ",");
		switch (state) {
		case AS_NUM:
		    int asNumber = (int) in.nval;
		    if (DEBUG) {
			System.out.print("AS #" + asNumber + " connected to ");
		    }
		    cellList.add(new AS(this));
		    ((AS) cellList.getLast()).asNumber = asNumber;
		    asLookupFromNum.put(new Integer(asNumber),new Integer(index)); // put into lookup table
		    state = AS_OUTDEGREE;
		    break;
		case AS_OUTDEGREE:
		    state = AS_NEIGHBOR;
		    break;
		case AS_NEIGHBOR:
		    int as = (int) in.nval;
		    if (DEBUG) {
			System.out.print(as + ",");
		    }
		    ((AS) cellList.getLast()).adjacencyList.add(new Integer(as));
		    break;
		}
		break;
	    case StreamTokenizer.TT_WORD:
		System.out.println("Unexpected input from file: " + in.sval);
		System.exit(1);
		break;
	    case StreamTokenizer.TT_EOL:
		index++;
		state = AS_NUM;
		if (DEBUG)
		    System.out.println("");
		break;
	    }
	}
	if (state != AS_NUM) { // no EOL before EOF
	    index++;
	}
	if (DEBUG)
	    System.out.println("");

	// Set #ASs
	numASs = index;

	// move the ASs from the cellList to the ASGraph array and initialize
	index = 0;
	ases = new AS[numASs];
	ListIterator it = cellList.listIterator();
	while (it.hasNext()) {
	    ases[index] = ((AS) it.next());
	    ases[index].init(index);
	    index++;
	}
	System.err.println("Loaded graph of " + numASs + " ASs");
    } // load()


    public class Edge {
	public int from;
	public int to;
	public Edge(int _from, int _to) {
	    from = _from; to = _to;
	}
	public String toString () {
	    return ("("+from+","+to+")");
	}
    }

    /**
     * Not used at the moment.
     */
    public class OutputManager {
	ASGraph g;

	/** For visualization and animation files. */
	PrintWriter out;
	public boolean     stdOut = false;
	public boolean     globalsOut = false;
	public boolean     namOut = false;
	public boolean     dmlOut = false;
	WormRecorder       ssfRecorder = null;
	boolean[]          crashMarked;
	private float      globalTimeStep;
	private float      lastGlobalTimeStep;
	private float      routeUpdateBinSize = 0F;
	private int        routeUpdateBinVal = 0;
	private float      prevBinTime = 0F;
	private boolean    routingRecordOff = false;


	public OutputManager(ASGraph myASGraph,
			     boolean _stdOut, boolean _globalsOut, 
			     boolean doLayout,
			     String _gmlFile, String _namFile, 
			     String _dmlFile) {
	    g = myASGraph;
	    stdOut = _stdOut; globalsOut = _globalsOut;
	    if (_namFile != null)
		namOut = true;

	    /*
	      if (doLayout)
	      layout();
	    */

	    try {
		if (_gmlFile != null)
		    outputGML(_gmlFile + ".gml");
		if (_dmlFile != null)
		    outputDML(_dmlFile);
	    } catch (IOException e) {
		System.err.println("ERROR: Couldn't create output file");
		System.err.println(e);
		System.exit(1);
	    }

	    if (globalsOut) {
		// init some globals
		globalTimeStep = MacroscopicModel.ref.DeltaT * 1000; // 1000 times the timestep
		lastGlobalTimeStep = 0;
	    }
	} // constructor

	public void close() {
	}

	/** Output current state. */
	void output() {
	    if (stdOut) {
		System.out.print(MacroscopicModel.ref.t);
		// separate for clarity
		for (int j = 0; j < numASs; j++) {
		    ases[j].output();
		}
		System.out.println("");
	    }

	    // visu output
	    if (dmlOut)
		outputDMLstate();
	}

	/**
	 *  Set bin size for "global" routing update reporting, i.e. that 
	 *  observed on core AS.
	 */
	void setBinSize(float binSize) {
	    routeUpdateBinSize = binSize;
	}

	/**
	 *  Switch off output of routing update animation records.
	 */
	void routingRecordOutputOff() {
	    routingRecordOff = true;
	}

	/**
	 *  Output graph in GML format
	 */
	void outputGML(String filename) throws IOException {
	    FileOutputStream os = new FileOutputStream(new File(filename));
	    out = new PrintWriter(os);

	    out.println("graph [");
	    // output nodes
	    for (int j=0; j<numASs; j++) {
		if (!ases[j].hasLayoutInfo) {
		    out.println("  node [ id " + ases[j].index + " ]");
		} else {
		    out.println("  node [ id " + ases[j].index 
				+ " graphics [ Image [ Type \"\" Location \"\" ] center [ x " + ases[j].x 
				+ " y " + ases[j].y + " z 0.0 "
				+ " ] width 10.0 height 10.0 depth 10.0 vgj [ labelPosition \"below\" shape \"Oval\" ] ] ]");
		}
	    }
	    // output edges
	    HashMap reverseEdges = new HashMap();
	    for (int j=0; j<g.numASs; j++) {
		ListIterator it = ases[j].adjacencyList.listIterator();
		while (it.hasNext()) {
		    Integer as = (Integer) it.next();
		    int from = j;
		    int to = ((Integer) asLookupFromNum.get(as)).intValue();
		    if (reverseEdges.get((new Edge(from, to).toString())) == null) {
			out.println("  edge [ source " + from
				    + " target " + to + " ]");
			reverseEdges.put((new Edge(to, from)).toString(), new Integer(1));
		    }
		}
	    }

	    out.println("]");

	    out.close();
	} // outputGML()


	/**
	 *  Output graph in DML format
	 */
	void outputDML(String filename) throws IOException {
	    FileOutputStream os = new FileOutputStream(new File(filename + ".dml"));
	    out = new PrintWriter(os);

	    float halfRect = MacroscopicModel.ref.layoutRectSize / 2;

	    out.println("Net [");
	    // output nodes
	    for (int j=0; j<numASs; j++) {
		int id = ases[j].index;
		if (!ases[j].hasLayoutInfo) {
		    out.println("  host [ id " + id 
				+ " interface [ id 0 ]"
				+ " ]");
		} else {
		    float x = ases[j].x + halfRect;
		    float y = ases[j].y + halfRect;
		    out.println("  host [ id " + id
				+ " interface [ id 0 ]"
				+ " graphics [ x " + x
				+ " y " + y + " ] ]");
		}
	    } // for
	    // output edges
	    HashMap reverseEdges = new HashMap();
	    for (int j=0; j<g.numASs; j++) {
		ListIterator it = ases[j].adjacencyList.listIterator();
		while (it.hasNext()) {
		    Integer as = (Integer) it.next();
		    int from = j;
		    int to = ((Integer) asLookupFromNum.get(as)).intValue();
		    if (reverseEdges.get((new Edge(from, to).toString())) == null) {
			out.println("  link [ attach " + from
				    + "(0) attach " + to + "(0) ]");
			reverseEdges.put((new Edge(to, from)).toString(), new Integer(1));
		    }
		}
	    }

	    out.println("]");

	    out.close();

	    // init visualization state
	    dmlOut = true; // set DML output flag
	    ssfRecorder = new WormRecorder(filename);
	    crashMarked = new boolean[numASs];
	    for (int i=0; i<numASs; i++) {
		crashMarked[i] = false;
	    }
	} // outputDML()


	/**
	 *  SSFNet visualization output.
	 */
	void outputDMLstate() {
	    // global state indicators
	    if (globalsOut && MacroscopicModel.ref.t 
		>= (lastGlobalTimeStep + globalTimeStep)) {
		// infections
		float totalInfectedHosts = 0;
		for (int j=0; j<g.numASs; j++) {
		    totalInfectedHosts += ases[j].wormModel.i;
		}
		ssfRecorder.
		    sendGlobalRecord(MacroscopicModel.ref.t, totalInfectedHosts);

		lastGlobalTimeStep = MacroscopicModel.ref.t;
	    } // if
	    if (globalsOut) {
		// specialized - pick an AS from the "core" and observe 
		// routing traffic
		int observeAS = -1;
		switch (numASs) {
		case 68: // "99%" graph
		    observeAS = 44; // index
		    break;
		case 194: // "94%" graph
		    observeAS = 18; // index
		    break;
		case 799: // "75%" graph
		    observeAS = 221; // index
		    break;
		default:
		}
		/*
		  if (observeAS >= 0) {
		  int activity = 
		  ases[observeAS].gwRoutingState.routeUpdates.size();
		  if (routeUpdateBinSize > 0) {
		  if (t < (prevBinTime + routeUpdateBinSize)) {
		  routeUpdateBinVal += activity;
		  } else {
		  if (routeUpdateBinVal > 0) {
		  ssfRecorder.
		  sendGlobalRoutingTrafficRecord(t, observeAS, routeUpdateBinVal);
		  }
		  prevBinTime = t;
		  routeUpdateBinVal = 0;
		  } 
		  } else {
		  if (activity > 0) {
		  ssfRecorder.
		  sendGlobalRoutingTrafficRecord(t, observeAS, activity);
		  } // if activity
		  }
		  }
		*/
	    }
	} // outputDMLstate()


	/*
	  public class VGJ_Graph {
	  public Graph g;

	  public VGJ_Graph(ASGraph asGraph) {
	  g = new Graph();

	  // insert nodes
	  for (int j=0; j<asGraph.numASs; j++) {
	  g.insertNode();
	  }
	  // insert edges
	  HashMap reverseEdges = new HashMap();
	  for (int j=0; j<asGraph.numASs; j++) {
	  ListIterator it = asGraph.ases[j].adjacencyList.listIterator();
	  while (it.hasNext()) {
	  Integer as = (Integer) it.next();
	  int from = j;
	  int to = ((Integer) asLookupFromNum.get(as)).intValue();
	  if (reverseEdges.get((new Edge(from, to).toString())) == null) {
	  g.insertEdge(from, to);
	  reverseEdges.put((new Edge(to, from)).toString(), new Integer(1));
	  }
	  } // while
	  } // for
	  } // constructor
	  } // VGJ_Graph


	  public class DummyUpdate implements GraphUpdate {
	  public void update(boolean adjust_bounds) { System.err.print("."); }
	  public void scale(double scaleval) {}
	  public void center() {}
	  public DRect windowRect() 
	  { return new DRect(0,0,
	                     MacroscopicModel.ref.layoutRectSize,
	                     MacroscopicModel.ref.layoutRectSize); }
	  public double getHSpacing() {return 0.0;}
	  public double getVSpacing() {return 0.0;}
	  public Frame getFrame() { return new Frame(); }
	  public Node getSelectedNode() { return new Node(); }
	  }
	*/

	/**
	 *  Do a layout of the graph using the VGJ Spring algorithm.
	 */
	/*
	  void layout() {
	  VGJ_Graph vgj = new VGJ_Graph(g);

	  // layout graph
	  DummyUpdate dummy = new DummyUpdate();
	  Spring s = new Spring();
	  System.err.println("Doing graph layout...");
	  //      s.compute(g, dummy);
	  s.compute(vgj.g, dummy);
	  System.err.println("");

	  // update AS nodes
	  for (int j=0; j<g.numASs; j++) {
	  //	DPoint p = g.getNodeFromIndex(j).getPosition();
	  DPoint p = vgj.g.getNodeFromIndex(j).getPosition();
	  ases[j].x = (float) p.x;
	  ases[j].y = (float) p.y;
	  ases[j].hasLayoutInfo = true;
	  }
	  } // layout()
	*/
    } // OutputManager

} // ASGraph

